package com.dyp.security_demo.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.session.web.http.HeaderHttpSessionIdResolver;

@Configuration
@EnableWebSecurity
/**
 * prePostEnabled属性决定Spring Security在接口前注解是否可用@PreAuthorize,@PostAuthorize等注解,设置为true,会拦截加了这些注解的接口
 * Determines if Spring Security's PreAuthorize, PostAuthorize, PreFilter, and PostFilter
 **/
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled=true)
public class SecurityConfig {
    @Autowired
    SessionAuthenticationStrategy sessionAuthenticationStrategy;

    @Autowired
    AuthenticationSuccessHandler authenticationSuccessHandler;

    @Autowired
    AuthenticationFailureHandler authenticationFailureHandler;

    @Autowired
    LogoutSuccessHandler logoutSuccessHandler;

    @Autowired
    AuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    SessionInformationExpiredStrategy sessionInformationExpiredStrategy;

    @Autowired
    AccessDeniedHandler accessDeniedHandler;
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        /**
         * CSRF 防护：Spring Security 默认对所有非 GET/HEAD/OPTIONS/TRACE 的请求要求验证 CSRF Token。
         * 所以未豁免 CSRF 检查的情况下，虽然 .permitAll() 放行了路径，但依旧会报403无权限返回。
         */
        http
                .cors(Customizer.withDefaults()) //允许跨域访问，前后端分离一定是跨域的
                .csrf(AbstractHttpConfigurer::disable)
                .formLogin(form -> form
                        .successHandler(authenticationSuccessHandler) // 登陆成功后如何给客户返回数据
                        .failureHandler(authenticationFailureHandler) // 登陆失败后如何给客户返回数据
                        .usernameParameter("username") //配置自定义表单用户名，默认username
                        .passwordParameter("password") //配置西定义表单密码，默认password
                )
                .logout(logout->logout.logoutSuccessHandler(logoutSuccessHandler))// 自定义登出返回信息，而不是默认跳转到login页面
                .exceptionHandling(exception-> exception
//                        .authenticationEntryPoint(authenticationEntryPoint) // 自定义未认证时的处理，而不是默认跳转到login页
                                .accessDeniedHandler(accessDeniedHandler) // 访问权限不足的接口如何处理，默认是返回403页面。通过/admin/role接口尝试
                )
                .sessionManagement(session->{
                    session.sessionAuthenticationStrategy(sessionAuthenticationStrategy); // 将session写入头部
//                    session.addSessionAuthenticationStrategy(sessionAuthenticationStrategy);
                    //一个账号只能登陆一次，后面的会挤掉前面的，测试这个需要两个浏览器测试
                    session.maximumSessions(1).expiredSessionStrategy(sessionInformationExpiredStrategy);
                })
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/v1/all").permitAll()
                        .requestMatchers( "/v1/user/role").hasRole("USER")
                        .requestMatchers("/v1/admin/role").hasRole("ADMIN")
                        .anyRequest().authenticated()
                );

        return http.build();
    }

    /**
     * 用户信息服务(配置用户账号、密码、角色)
     * @param passwordEncoder 密码加密器
     * @return 在内存用户详细信息管理器中
     */
    @Bean
    public InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) {
        UserDetails user = User.withUsername("user")
                .password(passwordEncoder.encode("123456"))
                .roles("USER")
                .build();

        UserDetails admin = User.withUsername("admin")
                .password(passwordEncoder.encode("123456"))
                .roles("USER", "ADMIN")
                .build();

        return new InMemoryUserDetailsManager(user, admin);
    }

    /**
     * 密码加密器  配置了InMemoryUserDetailsManager需要配置。
     * 如果不配置提示：Consider defining a bean of type 'org.springframework.security.crypto.password.PasswordEncoder' in your configuration.
     * @return 密码加密器的实例
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
//        public static PasswordEncoder createDelegatingPasswordEncoder() {
//            String encodingId = "bcrypt";
//            Map<String, PasswordEncoder> encoders = new HashMap();
//            encoders.put(encodingId, new BCryptPasswordEncoder());
//            encoders.put("ldap", new LdapShaPasswordEncoder());
//            encoders.put("MD4", new Md4PasswordEncoder());
//            encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
//            encoders.put("noop", NoOpPasswordEncoder.getInstance());
//            encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5());
//            encoders.put("pbkdf2@SpringSecurity_v5_8", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
//            encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1());
//            encoders.put("scrypt@SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
//            encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
//            encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
//            encoders.put("sha256", new StandardPasswordEncoder());
//            encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());
//            encoders.put("argon2@SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
//            return new DelegatingPasswordEncoder(encodingId, encoders);
//        }
//        默认创建的是encoders.put(encodingId, new BCryptPasswordEncoder());
        PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
        return encoder;
    }

    @Bean
    public HeaderHttpSessionIdResolver sessionIdResolver() {
        // 使用 X-Session-Id 头传递 Session ID
//        return HeaderHttpSessionIdResolver.xAuthToken(); // 默认用 X-Auth-Token
        // 或自定义头：
         return new HeaderHttpSessionIdResolver("X-Session-Id");
    }
}